######################################## Google 风格指南 ######################################## 本文是 `Google 开源项目风格指南 `_ 的简要小结,但是小部分加入了一些我的注释 头文件 **************************************** #. 除了 **main()** 函数和单元测试文件外,每个源文件都应当有对应的头文件 #. 头文件应该是 :abbr:`自包含 (Self-contained)` 的,所谓 **自包含** 是指外部引用时不需要为特殊场合包含额外的头文件 #. 内联函数和模板应当在头文件中添加定义而不是在源文件中 .. note:: - 对于 MSVC 而言,内联函数必须在头文件中实现,否则会编译失败 - 如果某函数模板为所有相关模板参数显式实例化,或是某个类的私有成员,那么它就只能在源文件中实现 #. 头文件应当有 **#define** 或 **#prgma once** 的保护,以防止头文件被多次包含 .. tip:: **#define** 的命名格式为 ``___H_`` ,其中 **** 相对于项目的源代码路径 #. 除了本项目的实体外,函数、类模板等应当避免使用 `前置声明 `_ .. note:: 使用前置声明引入的类是不完全类型,我们只能 - 使用类的指针或引用 - **声明** 以不完全类型作为参数或返回值的函数 不完全类型不能用来创建对象或引用其函数。因此一般在头文件中使用前置声明 不要对标准库中的类使用前置类型声明 #. 只有函数代码少于十行时才会被定义为内联函数 ( *析构函数* 、 *递归函数* 和 *虚函数* 不应当定义为内联函数) #. **#include** 应当按照以下顺序包含头文件: #. 源文件对应的头文件 #. C 系统头文件 #. C++ 系统头文件 #. 其他库中的头文件 #. 本项目中的头文件 #. 按条件包含的头文件 在每个类别中应当插入空行进行分隔 作用域 **************************************** #. 命名空间 - 在 **源文件** 中建议使用 *匿名命名空间* 或 *static 声明* 。但是头文件不允许 - 具名的命名空间其名字应当基于项目名或相对路径 - 禁止使用 *using* 和 *内敛命名空间* - 禁止在头文件中使用 *命名空间别名* - 命名空间别名应当只在实现中使用 #. 函数和变量 - 非成员函数应当总是放在命名空间中 - 不要使用类的静态函数模拟命名空间的效果 - 类的静态方法应当只和类的实例或静态数据相关 - 变量的作用域应当尽量缩小。只在需要使用变量时才声明它 - 应当使用初始化的方式声明变量而不是先声明再赋值(当在循环中使用对象时例外) - 禁止创建非 `POD `_ 类型的 static 变量和全局变量 ( *constexpr* 变量除外) - 禁止使用含有副作用的函数初始化 POD 全局变量 - 禁止使用函数返回值初始化 POD 变量(除非函数返回值不涉及任何全局变量) .. note:: 上述三条主要是防止在多编译单元中变量初始化顺序的不确定性。但在同一个编译单元内,静态初始化优先于动态初始化,初始化顺序与声明顺序相同。 - 为了改善静态变量和全局对象析构的不确定性,可以使用 **quick_exit()** 代替 **exit()** 终止程序,其不会执行析构过程。 类 **************************************** #. 不要在构造函数中以任何形式(直接调用或通过 init() 间接调用)调用虚函数 #. 不要在构造函数中执行过多的逻辑相关的初始化 #. 编译器提供的默认构造函数不会对变量进行初始化 #. 不要在无法报告错误的情况下在 **构造函数** 中调用可能会出错的函数 #. 构造函数的地址无法被获得 #. 构造函数中不得报告非致命错误 #. 不要定义隐式类型转换。转换运算符和单参数构造函数应当使用 *explicit* 修饰 .. note:: 如果单参数构造函数没有使用 *explicit* 那么就无法判断这个函数是用作类型转换还是创建对象的。 #. 如果不需要类支持拷贝和移动,就把它们删掉。 这里请参阅 `可拷贝类型和可移动类型 `_ #. 子类重载虚函数时也应该加上 virtual 关键字 #. 只有仿函数和在仅有数据成员时使用结构体,否则使用类 #. 除非是公有继承,否则一律使用组合 #. 当出现多重继承时,最多只能有一个基类是具体类,其他基类必须都为接口类。(除了 `部分情况 `_ ) .. note:: 所谓接口类,就是只有纯虚函数和静态函数,没有非静态数据成员,没有定义任何带参数的、非 *protect* 的构造函数。 接口类建议使用 *Interface* 结尾。 #. 尽量不要重载运算符,也不要创建用户自定义字面量 #. 没有副作用的二元运算符不应当定义为成员函数 #. 不要重载 **&&** 、 **||** 、 **,** 和 **&** 。也不要重载 **operator""** #. 除了 **static const** 类型外,所有数据成员声明为 **private** #. 控制域应当遵循 public -> protected -> private,将相似的声明放在最后 #. 存取函数一般内联在头文件中。 .. hint:: 所谓存取函数,是指存取数据成员的 getter 和 setter 。而不是容器的 push 和 pop 函数 **************************************** - 在函数声明时,输入参数(通常由 const 修饰)要放到输出参数(通常是非 const 指针)前面 - 函数体的长度一般不应该大于 40 行 - 所有按引用传递的参数必须加上 const - 若要使用函数重载, 应当让用户一看函数签名就知道怎么用, 而不是花心思猜测调用的重载函数到底是哪一种。这一规则也适用于构造函数 .. note:: 如果函数的参数数量相同,与其重载一个函数(例如 Append() ),不如在函数名上加上参数信息。(比如 AppendString、AppendInt 等) #. 禁止在纯虚函数中使用默认参数。在函数重载时 **必须** 保证默认参数的值相同。 .. note:: 对于默认参数而言,以下需要注意: - 默认参数时函数重载的另一种语义,一般情况下使用函数重载而不是默认参数 - 默认参数在每个调用点都需要进行重新求值,这会导致生成的代码迅速膨胀 - 缺省参数会干扰函数指针, `导致函数签名与调用点的签名不一致 `_ 。 而函数重载不会导致这样的问题 - 如果使用函数指针调用带有默认参数的函数,那么默认参数无效。这是因为默认参数是在运行时求值,而函数调用是编译时。 - 默认参数值一旦改变,所有设计到函数的文件都必须重新编译 - 只为可以缺省的参数提供默认值,不要为了调用时省劲就用默认参数 [#]_ 除以下情况外 **非常不建议** 使用默认参数: [#]_ - 源文件中的静态函数或者匿名空间函数 - 构造函数 - 用来模拟变长数组 例如: .. code-block:: cpp // 通过空 AlphaNum 以支持四个形参 string StrCat(const AlphaNum &a, const AlphaNum &b = gEmptyAlphaNum, const AlphaNum &c = gEmptyAlphaNum, const AlphaNum &d = gEmptyAlphaNum); #. 只有在不方便使用前置类型声明时采用后置类型声明 .. note:: C++ 的前置类型声明的方法是: .. code-block:: C++ int foo(int x); 后置类型写法是: .. code-block:: C++ auto foo(int x) -> int; 只有在前置类型声明不方便的时候才使用后置类型声明(比如 Lambda 表达式) #. 只有在移动构造和移动赋值时才使用右值引用。不要使用 **std::forward** #. 不允许使用变长数组和 **alloca()** #. 不允许使用 C 风格的类型转换 #. 只在记录日志时使用流,其他时候使用 *printf* 和 *read* .. note:: 事实上,流使我们在打印时不在关心对象的类型,但是这也导致用错代码时编译器不会有任何警告。而且流不做特殊处理的时候效率是很低的。尽管我们可以一边用流一边用 printf ,但是根据 **一致性原则** ,我们除了日志接口外,其余部分都用 *printf* 和 *read* 以统一 IO 接口。 #. 对于迭代器和其他模板对象而言,只使用前置自增 #. 尽可能使用 **const** 和 **constexpr** 。 const 建议放在类型前面。 #. 在声明 const 对象的同时对对象进行初始化。 #. 关键字 **mutable** 是线程不安全的,谨慎使用、 #. 对于整型而言,只使用 **int** 、 **int8_t** 、 **int64_t** 等。(位于 *cstdint* ) #. 64 位下的可移植性 .. note:: 此部分主要针对打印,参见: `64 位下的可移植性 `_ #. 尽量使用内联函数、枚举和常量代替宏 - 不要在头文件中定义宏 - 只有在马上使用时才 **#define** ,之后立即 **#undef** - 不要对一个已经存在的宏使用 **#undef** ,选择一个不会冲突的名称 - 不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为 - 不要用 **##** 处理函数,类和变量的名字 #. 对于空值而言:整数用 *0* ,实数用 *0.0* ,指针用 *nullptr* 或 *NULL* 、字符用 *'\\0'* #. 尽量使用 **sizeof(变量名)** 而不是 **sizeof(类型名)** #. 在保证可读性良好的情况下使用 **auto** - auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量。 #. 使用列表初始化或双括号以避免歧义。使用列表初始化而不是圆括号以避免隐式类型转换。 #. Lambda 不要捕获全部,只捕获需要的部分 #. 不要使用过于复杂的模板编程 #. Boost 只使用以下库: - Call Traits : *boost/call_traits.hpp* - Compressed Pair : *boost/compressed_pair.hpp* - `_ 其他内容 **************************************** 以下内容是 Google 为了兼容已有代码或兼容社区成员而考虑的,请酌情考虑是否使用: #. 不允许使用异常 #. 不允许使用 RTTI .. seealso:: - `对使用 C++ 异常处理应具有怎样的态度? `_ - `Should the trailing return type syntax style become the default for new C++11 programs? `_ .. [#] `Google C++ Style Guide 中为什么禁止使用缺省函数参数? `_ .. [#] `缺省参数 `_ .. [#] `命名规则的特例 `_